package com.minigame.card.paper.util;

import com.minigame.card.enums.Direction;
import com.minigame.card.paper.domain.PaperCardPlayer;
import com.minigame.card.paper.enums.PaperCard;

import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.core.collection.CollUtil.countMap;
import static com.minigame.card.util.CardUtil.check;
import static com.minigame.card.util.CardUtil.init;
import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;

public class PaperUtil {

    public static double countJin(List<PaperCard> up, List<List<PaperCard>> down, PaperCard discard) {
        double sum = 0;
        if (discard != null) {
            up.add(discard);
        }
        Map<PaperCard, Integer> upCountMap = countMap(up);
        for (PaperCard card : up) {
            if (card.isJin()) {
                sum++;
            }
        }
        List<PaperCard> cards = upCountMap.keySet().stream().filter(card -> !card.isJin()).collect(Collectors.toList());
        for (PaperCard card : cards) {
            if (upCountMap.get(card) >= 3) {
                sum += discard == card ? 0.5 : 1;
            }
        }
        if (down != null) {
            for (List<PaperCard> solid : down) {
                Map<PaperCard, Integer> solidCountMap = countMap(solid);
                if (solidCountMap.keySet().stream().noneMatch(PaperCard::isJin) && solidCountMap.keySet().size() == 1) {
                    for (Integer count : solidCountMap.values()) {
                        if (count == 3) {
                            sum += 0.5;
                        } else if (count == 4){
                            sum += 1.5;
                        }
                    }
                }
                for (PaperCard card : solid) {
                    if (card.isJin()) {
                        sum++;
                    }
                }
            }
        }
        if (discard != null) {
            up.remove(up.size() - 1);
        }
        return sum;
    }

    public static Map<Direction, PaperCardPlayer> paperCardInitPlayers(Direction host, Direction rest, List<PaperCard> messList) {
        Map<Direction, List<PaperCard>> playersUpCards = init(messList, host, 19, d -> d == rest, comparing(PaperCard::getIndex));
        Map<Direction, PaperCardPlayer> playerMap = new LinkedHashMap<>();
        playersUpCards.forEach((direction, cards) -> {
            PaperCardPlayer player = new PaperCardPlayer();
            player.setDirection(direction);
            player.setUp(cards);
            playerMap.put(direction, player);
        });
        return playerMap;
    }
    /**
     * 判定玩家是否胡牌
     */
    public static <T> boolean tryWin(List<PaperCard> playerCards) {
        Map<Integer, List<PaperCard>> groupMap = groupOf(playerCards);
        int retainTwoCount = 0;
        for (List<PaperCard> groupList : groupMap.values()) {
            int size = retainOfSolid(countMap(groupList)).size();
            if (size == 2) {
                retainTwoCount ++;
            } else if (size != 0){
                return false;
            }
        }
        return retainTwoCount == 1;
    }
    /**
     * 每组计算元或坎后剩余牌, 如 [上,上,上,大,大,大,人,人] -> [上,大]
     */
    public static <T> List<T> retainOfSolid(Map<T, Integer> countMap) {
        if (is223(countMap)) {
            return retainByUnity(countMap);
        }
        List<T> byRepeat = retainByRepeat(countMap);
        return retainByUnity(byRepeat.stream().collect(toMap(identity(), v -> 1, Integer::sum)));
    }

    /**
     * 按元计算, 如 [上,上,上,大,大,大,人,人] -> [上,大]
     */
    public static  <T> List<T> retainByUnity(Map<T, Integer> countMap) {
        if (countMap.isEmpty()) {
            return emptyList();
        }
        if (countMap.keySet().size() < 3) {
            List<T> retain = new ArrayList<>();
            countMap.forEach((k, v) -> {
                for (int i = 0; i < v; i++) {
                    retain.add(k);
                }
            });
            return retain;
        }
        int min = Collections.min(countMap.values());
        List<T> retain = new ArrayList<>();
        countMap.forEach((k, v) -> {
            if (v >= 1) {
                int n = v - min;
                for (int i = 0; i < n; i++) {
                    retain.add(k);
                }
            }
        });
        return retain;
    }

    /**
     * 按坎计算, 如 [上,上,上,大,大,大,人,人] -> [人,人]
     */
    public static  <T> List<T> retainByRepeat(Map<T, Integer> countMap) {
        List<T> retain = new ArrayList<>();
        countMap.forEach((k, v) -> {
            int n = v % 3;
            for (int i = 0; i < n; i++) {
                retain.add(k);
            }
        });
        return retain;
    }

    /**
     * 组合是否223, 如: 上上大大人人人
     * 只有223只能按元处理, 不能按坎处理
     */
    public static <T> boolean is223(Map<T, Integer> countMap) {
        return countMap.values().containsAll(Arrays.asList(2,2,3));
    }

    /**
     * 玩家牌分组
     */
    public static Map<Integer, List<PaperCard>> groupOf(List<PaperCard> playerCards) {
        return playerCards.stream().collect(groupingBy(PaperCard::getGroup));
    }


    /**
     * 去掉2张关联牌后, 判断是否胡牌 [上大人纸牌]
     * @param counts 每张牌数量
     */
    public static boolean paperCardHu(int[] counts) {
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] < 1) {
                continue;
            }
            //上上
            boolean pair = counts[i] >= 2;
            //上大
            boolean neighbor = i < counts.length - 1 && counts[i + 1] >= 1 && i / 3 == (i + 1) / 3;
            //上人
            boolean gap = i < counts.length - 2 && counts[i + 2] >= 1 && i / 3 == (i + 2) / 3;
            int j = pair ? i : (neighbor ? (i + 1) : (gap ? (i + 2) : -1));
            if (j != -1) {
                if (checkWithoutTwo(counts, i, j)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     *
     * @param counts 计数数组
     * @param i 第一张牌索引
     * @param j 第二张牌索引
     */
    public static boolean checkWithoutTwo(int[] counts, int i, int j) {
        counts[i]--;
        counts[j]--;
        if (check(counts, counts.length, (index) -> index % 3 == 0)) {
            return true;
        }
        counts[i]++;
        counts[j]++;
        return false;
    }

    /**
     * 能否吃
     * @param counts 计数数组
     * @param fetchIndex 获取值
     */
    public static int[] tryPaperCardCh(int[] counts, int fetchIndex) {
        int mod = fetchIndex % 3;
        if (mod == 0 && counts[fetchIndex + 1] > 0 && counts[fetchIndex + 2] > 0) {
            return new int[]{fetchIndex, fetchIndex + 1, fetchIndex + 2};
        }
        if (mod == 1 && counts[fetchIndex - 1] > 0 && counts[fetchIndex + 1] > 0) {
            return new int[]{fetchIndex - 1, fetchIndex, fetchIndex + 1};
        }
        if (mod == 2 && counts[fetchIndex - 2] > 0 && counts[fetchIndex - 1] > 0) {
            return new int[]{fetchIndex - 2, fetchIndex - 1, fetchIndex};
        }
        return null;
    }

    /**
     * 能否碰
     * @param counts 计数数组
     * @param fetchIndex 碰牌
     */
    public static boolean tryPaperCardPong(int[] counts, int fetchIndex) {
        return hasRepeatCount(counts, fetchIndex, 2);
    }

    /**
     * 能否杠
     * @param counts 计数数组
     * @param fetchIndex 杠牌
     */
    public static boolean tryPaperCardGang(int[] counts, int fetchIndex) {
        return hasRepeatCount(counts, fetchIndex, 3);
    }

    /**
     * 能否暗杠
     * @param counts 计数数组
     */
    public static int[] tryPaperCardAnGang(int[] counts) {
        int[] indexes = new int[0];
        int j = 0;
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] == 4) {
                indexes = Arrays.copyOf(indexes, indexes.length + 1);
                indexes[indexes.length - 1] = i;
            }
        }
        return indexes;
    }

    /**
     * 上家打的牌, 是否有多张相同牌
     * @param counts 计数数组
     * @param fetchIndex 吃碰杠牌
     * @param n 相同牌数
     */
    public static boolean hasRepeatCount(int[] counts, int fetchIndex, int n) {
        return counts[fetchIndex] >= n;
    }

    /**
     * 手上和落地的牌交换
     * @param up 手上牌
     * @param down 落地牌
     * @param solid 固定牌型
     */
    public static void paperCardExchange(PaperCard discard, List<PaperCard> up, List<PaperCard> down, List<PaperCard> solid) {
        if (down == null) {
            down = new ArrayList<>();
        }
        up.add(discard);
        for (PaperCard card : solid) {
            up.remove(card);
            down.add(card);
        }
    }
}
